package gsw.tool.pluggable.am;

import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * 正在运行的代理进程列表。
 * 用来维护正在运行的进程和组件，保存目标进程和组件信息，即实际需要运行的进程和组件信息。
 */
class RunningProcessList {

    private static final String TAG = RunningProcessList.class.getSimpleName();
    //保存正在运行的代理进程的ID与信息，默认大小为5.
    @SuppressLint("UseSparseArrays")
    private Map<Integer, ProcessItem> items = new HashMap<Integer, ProcessItem>(5);
    //组件信息的比较器
    private static Comparator sComponentInfoComparator = new Comparator<ComponentInfo>() {
        @Override
        public int compare(ComponentInfo lhs, ComponentInfo rhs) {
            /**
             * Collator 用来简化我们处理各种语言之间的差别性。Collator 主要处理：
             * 规范化的典型等效字符
             * 多层次的比较
             */
            return Collator.getInstance().compare(lhs.name, rhs.name);
        }
    };
    //Provider组件信息的比较器
    private static Comparator sProviderInfoComparator = new Comparator<ProviderInfo>() {
        @Override
        public int compare(ProviderInfo lhs, ProviderInfo rhs) {
            return Collator.getInstance().compare(lhs.authority, rhs.authority);
        }
    };

    /**
     * 根据插件的组件信息查找可用的代理进程
     *
     * @param targetInfo 目标组件信息，即插件实际需要运行的组件信息
     * @return 可用的代理进程名
     */
    public String getStubProcessByTarget(ComponentInfo targetInfo) {
        Log.i(TAG, "getStubProcessByTarget: ");
        for (ProcessItem processItem : items.values()) {
            //如果插件组件的包名、进程名与代理进程的包名列表、目标进程名一致，就返回该代理进程
            if (processItem.pkgs.contains(targetInfo.packageName) && TextUtils.equals(processItem.targetProcessName, targetInfo.processName)) {
                return processItem.stubProcessName;
            }
        }
        return null;
    }

    /**
     * 根据进程ID移除缓存表中代理进程
     *
     * @param pid 进程ID
     * @return 移除的代理进程
     */
    ProcessItem removeByPid(int pid) {
        return items.remove(pid);
    }

    void addActivityInfo(int pid, int uid, ActivityInfo stubInfo, ActivityInfo targetInfo) {
        Log.i(TAG, "addActivityInfo: ");
        ProcessItem item = items.get(pid);
        if (TextUtils.isEmpty(targetInfo.processName)) {
            targetInfo.processName = targetInfo.packageName;
        }
        if (item == null) {
            item = new ProcessItem();
            item.pid = pid;
            item.uid = uid;
            items.put(pid, item);
        }
        item.stubProcessName = stubInfo.processName;
        if (!item.pkgs.contains(targetInfo.packageName)) {
            item.pkgs.add(targetInfo.packageName);
        }
        item.targetProcessName = targetInfo.processName;
        item.addActivityInfo(stubInfo.name, targetInfo);
    }

    void removeActivityInfo(int pid, int uid, ActivityInfo stubInfo, ActivityInfo targetInfo) {
        Log.i(TAG, "removeActivityInfo: ");
        ProcessItem item = items.get(pid);
        if (TextUtils.isEmpty(targetInfo.processName)) {
            targetInfo.processName = targetInfo.packageName;
        }
        if (item != null) {
            item.removeActivityInfo(stubInfo.name, targetInfo);
        }
    }

    /**
     * 添加正在运行的代理进程到缓存表中
     *
     * @param pid 进程ID
     * @param uid 用户ID
     */
    void addItem(int pid, int uid) {
        Log.i(TAG, "addItem: ");
        ProcessItem item = items.get(pid);
        //如果不在缓存表中，就添加进去
        if (item == null) {
            item = new ProcessItem();
            item.pid = pid;
            item.uid = uid;
            item.startTime = System.currentTimeMillis();
            items.put(pid, item);
            //如果已经存在缓存表中，就更新数据
        } else {
            item.pid = pid;
            item.uid = uid;
            item.startTime = System.currentTimeMillis();
        }
    }

    /**
     * 判断代理进程是否正在运行
     *
     * @param stubProcessName 代理进程名
     * @return 代理进程是否正在运行
     */
    boolean isProcessRunning(String stubProcessName) {
        Log.i(TAG, "isProcessRunning: ");
        for (ProcessItem processItem : items.values()) {
            if (TextUtils.equals(stubProcessName, processItem.stubProcessName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断插件包能否运行在代理进程中
     *
     * @param packageName       插件的包名
     * @param stubProcessName   代理进程名
     * @param targetProcessName 目标进程名
     * @return 能否运行
     */
    boolean isPkgCanRunInProcess(String packageName, String stubProcessName, String targetProcessName) throws RemoteException {
        Log.i(TAG, "isPkgCanRunInProcess: ");
        for (ProcessItem item : items.values()) {
            if (TextUtils.equals(stubProcessName, item.stubProcessName)) {

                if (!TextUtils.isEmpty(item.targetProcessName) && !TextUtils.equals(item.targetProcessName, targetProcessName)) {
                    continue;
                }

                if (item.pkgs.contains(packageName)) {
                    return true;
                }

                boolean signed = false;
                if (signed) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 判断代理进程正在运行的插件包数量是否为0
     *
     * @param stubProcessName 代理进程名
     * @return 可运行的插件包数量是否为0
     */
    boolean isPkgEmpty(String stubProcessName) {
        Log.i(TAG, "isPkgEmpty: ");
        for (ProcessItem item : items.values()) {
            if (TextUtils.equals(stubProcessName, item.stubProcessName)) {
                return item.pkgs.size() <= 0;
            }
        }
        return true;
    }

    /**
     * 判断代理Activity是否被使用。
     * 先判断该代理进程是否正在运行，然后判断，该代理Activty是否正在运行，如果没有运行，就表示可用。
     *
     * @param stubInfo        代理Activity的存档信息
     * @param targetInfo      插件Activity的存档信息
     * @param stubProcessName 代理进程名
     * @return 代理Activity是否被使用
     */
    boolean isStubInfoUsed(ActivityInfo stubInfo, ActivityInfo targetInfo, String stubProcessName) {
        Log.i(TAG, "isStubInfoUsed: ");
        for (Integer pid : items.keySet()) {
            ProcessItem item = items.get(pid);
            //先判断该代理进程是否正在运行
            if (TextUtils.equals(item.stubProcessName, stubProcessName)) {
                //该代理Activity正在代理运行的所有插件Activity
                Set<ActivityInfo> infos = item.activityInfosMap.get(stubInfo.name);
                if (infos != null && infos.size() > 0) {
                    //然后判断，该代理Activty是否正在运行
                    for (ActivityInfo info : infos) {
                        //如果该插件Activity已经运行在该代理Activity中，那么该代理Actviity还是可用的
                        if (TextUtils.equals(info.name, targetInfo.name) && TextUtils.equals(info.packageName, targetInfo.packageName)) {
                            return false;
                        }
                    }
                    return true;
                }
                return false;
            }
        }
        return false;
    }

    /**
     * 根据进程ID获取可运行的插件包的列表
     *
     * @param pid 进程列表
     * @return
     */
    List<String> getPackageNameByPid(int pid) {
        Log.i(TAG, "getPackageNameByPid: ");
        ProcessItem item = items.get(pid);
        return item != null ? item.pkgs : new ArrayList<String>();
    }

    /**
     * 根据进程ID获取目标进程名
     *
     * @param pid 进程ID
     * @return 目标进程名
     */
    String getTargetProcessNameByPid(int pid) {
        Log.i(TAG, "getTargetProcessNameByPid: ");
        ProcessItem item = items.get(pid);
        return item != null ? item.targetProcessName : null;
    }

    /**
     * 根据进程ID获取代理进程名
     *
     * @param pid 进程ID
     * @return 代理进程名
     */
    public String getStubProcessNameByPid(int pid) {
        Log.i(TAG, "getStubProcessNameByPid: ");
        ProcessItem item = items.get(pid);
        return item != null ? item.stubProcessName : null;
    }

    /**
     * 设置代理进程代理的插件的进程名和包名
     * 通过代理组件信息查找代理进程，然后设置信息。
     *
     * @param stubInfo   代理组件信息
     * @param targetInfo 插件组件信息
     */
    void setTargetProcessName(ComponentInfo stubInfo, ComponentInfo targetInfo) {
        Log.i(TAG, "setTargetProcessName: ");
        for (ProcessItem item : items.values()) {
            if (TextUtils.equals(item.stubProcessName, stubInfo.processName)) {
                //设置代理进程运行插件的包名
                if (!item.pkgs.contains(targetInfo.packageName)) {
                    item.pkgs.add(targetInfo.packageName);
                }
                //设置代理进程的目的进程名
                item.targetProcessName = targetInfo.processName;
            }
        }
    }

    /**
     * 设置代理进程的信息(包括代理进程名、目标进程名和包名)
     *
     * @param pid               进程ID
     * @param stubProcessName   代理进程名
     * @param targetProcessName 目标进程名
     * @param targetPkg         目标包名
     */
    void setProcessName(int pid, String stubProcessName, String targetProcessName, String targetPkg) {
        Log.i(TAG, "setProcessName: ");
        ProcessItem item = items.get(pid);
        if (item != null) {
            if (!item.pkgs.contains(targetPkg)) {
                item.pkgs.add(targetPkg);
            }
            //目标进程名，即插件实际需要运行的进程名
            item.targetProcessName = targetProcessName;
            //代理进程名
            item.stubProcessName = stubProcessName;//
        }
    }

    //清除正在运行的代理进程列表
    void clear() {
        Log.i(TAG, "clear: ");
        items.clear();
    }

    /**
     * 正在运行的代理进程信息。
     * 我们使用stubProcessName(代理进程名)来标识这个代理进程，使用targetProcessName(目标进程名)来标识代理的插件。
     * 只要目标进程名一样，就可以运行改代理插件，所有可能有多个插件，多个包。然，我们应该尽量保证一个进程只允许一个插件。
     */
    private class ProcessItem {
        //代理进程名
        private String stubProcessName;
        //目标进程名，即插件组件指定的进程名，我们统一把插件的进程名设为它的包名
        private String targetProcessName;
        //进程ID
        private int pid;
        //用户ID
        private int uid;
        //代理进程的开始启动时间
        private long startTime;
        //被代理的插件的包名。只要目标进程名一样，就可以运行改代理插件，所以可能有多个插件，多个包。然，我们应该尽量保证一个进程只允许一个插件。
        private List<String> pkgs = new ArrayList<String>(1);

        //正在运行的插件ActivityInfo
        //key=ActivityInfo.name, value=插件的ActivityInfo,
        private Map<String, ActivityInfo> targetActivityInfos = new HashMap<String, ActivityInfo>(4);

        //正在运行的插件ProviderInfo
        //key=ProviderInfo.authority, value=插件的ProviderInfo
        private Map<String, ProviderInfo> targetProviderInfos = new HashMap<String, ProviderInfo>(1);

        //正在运行的插件ServiceInfo
        //key=ServiceInfo.name, value=插件的ServiceInfo
        private Map<String, ServiceInfo> targetServiceInfos = new HashMap<String, ServiceInfo>(1);

        //目标插件ActivityInfo与代理ActivityInfo的映射：一个代理Activity可能代理运行多个插件Activity
        //key=代理ActivityInfo.name, value=插件的ActivityInfo.name,
        private Map<String, Set<ActivityInfo>> activityInfosMap = new HashMap<String, Set<ActivityInfo>>(4);

        //正在运行的插件ProviderInfo与代理ProviderInfo的映射
        //key=代理ProviderInfo.authority, value=插件的ProviderInfo.authority,
        private Map<String, Set<ProviderInfo>> providerInfosMap = new HashMap<String, Set<ProviderInfo>>(4);

        //正在运行的插件ServiceInfo与代理ServiceInfo的映射
        //key=代理ServiceInfo.name, value=插件的ServiceInfo.name,
        private Map<String, Set<ServiceInfo>> serviceInfosMap = new HashMap<String, Set<ServiceInfo>>(4);

        /**
         * 更新包名，重新设置进程中所有组件的包名
         */
        private void updatePkgs() {
            Log.i(TAG, "updatePkgs: ");
            ArrayList<String> newList = new ArrayList<String>();
            for (ActivityInfo info : targetActivityInfos.values()) {
                newList.add(info.packageName);
            }

            for (ServiceInfo info : targetServiceInfos.values()) {
                newList.add(info.packageName);
            }

            for (ProviderInfo info : targetProviderInfos.values()) {
                newList.add(info.packageName);
            }
            pkgs.clear();
            pkgs.addAll(newList);
        }

        /**
         * 添加正在运行的代理Activity存档信息
         *
         * @param stubActivityName 代理Activity名
         * @param info             插件Activity存档信息
         */
        private void addActivityInfo(String stubActivityName, ActivityInfo info) {
            Log.i(TAG, "addActivityInfo: ");
            if (!targetActivityInfos.containsKey(info.name)) {
                targetActivityInfos.put(info.name, info);
            }

            //pkgs
            if (!pkgs.contains(info.packageName)) {
                pkgs.add(info.packageName);
            }

            //stub map to activity info
            Set<ActivityInfo> list = activityInfosMap.get(stubActivityName);
            if (list == null) {
                list = new TreeSet<ActivityInfo>(sComponentInfoComparator);
                list.add(info);
                activityInfosMap.put(stubActivityName, list);
            } else {
                list.add(info);
            }
        }

        void removeActivityInfo(String stubActivityName, ActivityInfo targetInfo) {
            Log.i(TAG, "removeActivityInfo: ");
            targetActivityInfos.remove(targetInfo.name);
            //remove form map
            if (stubActivityName == null) {
                for (Set<ActivityInfo> set : activityInfosMap.values()) {
                    set.remove(targetInfo);
                }
            } else {
                Set<ActivityInfo> list = activityInfosMap.get(stubActivityName);
                if (list != null) {
                    list.remove(targetInfo);
                }
            }
            updatePkgs();
        }

        private void addServiceInfo(String stubServiceName, ServiceInfo info) {
            if (!targetServiceInfos.containsKey(info.name)) {
                targetServiceInfos.put(info.name, info);

                if (!pkgs.contains(info.packageName)) {
                    pkgs.add(info.packageName);
                }

                //stub map to activity info
                Set<ServiceInfo> list = serviceInfosMap.get(stubServiceName);
                if (list == null) {
                    list = new TreeSet<ServiceInfo>(sComponentInfoComparator);
                    list.add(info);
                    serviceInfosMap.put(stubServiceName, list);
                } else {
                    list.add(info);
                }
            }
        }

        void removeServiceInfo(String stubServiceName, ServiceInfo targetInfo) {
            targetServiceInfos.remove(targetInfo.name);
            //remove form map
            if (stubServiceName == null) {
                for (Set<ServiceInfo> set : serviceInfosMap.values()) {
                    set.remove(targetInfo);
                }
            } else {
                Set<ServiceInfo> list = serviceInfosMap.get(stubServiceName);
                if (list != null) {
                    list.remove(targetInfo);
                }
            }
            updatePkgs();
        }


        private void addProviderInfo(String stubAuthority, ProviderInfo info) {
            if (!targetProviderInfos.containsKey(info.authority)) {
                targetProviderInfos.put(info.authority, info);

                if (!pkgs.contains(info.packageName)) {
                    pkgs.add(info.packageName);
                }

                //stub map to activity info
                Set<ProviderInfo> list = providerInfosMap.get(stubAuthority);
                if (list == null) {
                    list = new TreeSet<ProviderInfo>(sProviderInfoComparator);
                    list.add(info);
                    providerInfosMap.put(stubAuthority, list);
                } else {
                    list.add(info);
                }
            }
        }

        void removeProviderInfo(String stubAuthority, ProviderInfo targetInfo) {
            targetProviderInfos.remove(targetInfo.authority);
            //remove form map
            if (stubAuthority == null) {
                for (Set<ProviderInfo> set : providerInfosMap.values()) {
                    set.remove(targetInfo);
                }
            } else {
                Set<ProviderInfo> list = providerInfosMap.get(stubAuthority);
                if (list != null) {
                    list.remove(targetInfo);
                }
            }

            updatePkgs();
        }
    }
}
